package jsocks;

import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import socks.*;

public class SocksEcho extends Frame implements ActionListener, Runnable, WindowListener
{

//GUI components
    TextField host_text,
        port_text,
        input_text;
    Button proxy_button,
        accept_button,
        clear_button,
        connect_button,
        udp_button,
        quit_button;
    TextArea output_textarea;
    Label status_label;
    SocksDialog socks_dialog;
//Network related members
    Proxy proxy = null;
    int port;
    String host;
    Thread net_thread = null;
    InputStream in = null;
    OutputStream out = null;
    Socket sock = null;
    ServerSocket server_sock = null;
    Socks5DatagramSocket udp_sock;
    Object net_lock = new Object();
    int mode = COMMAND_MODE;
    //Possible mode states.
    static final int LISTEN_MODE = 0;
    static final int CONNECT_MODE = 1;
    static final int UDP_MODE = 2;
    static final int COMMAND_MODE = 3;
    static final int ABORT_MODE = 4;
    //Maximum datagram size
    static final int MAX_DATAGRAM_SIZE = 1024;

// Constructors
////////////////////////////////////
    public SocksEcho()
    {
        super("SocksEcho");
        guiInit();
        socks_dialog = new SocksDialog(this);
        socks_dialog.useThreads = false;


        URL icon_url = SocksEcho.class.getResource("SocksEcho.gif");
        if (icon_url != null)
        {
            try
            {
                Object content = icon_url.getContent();
                if (content instanceof java.awt.image.ImageProducer)
                {
                    setIconImage(createImage((java.awt.image.ImageProducer) content));
                }
            }
            catch (IOException ioe)
            {
            }
        }

        addWindowListener(this);
        Component component[] = getComponents();
        for (int i = 0; i < component.length; ++i)
        {
            if (component[i] instanceof Button)
            {
                ((Button) component[i]).addActionListener(this);
            }
            else if (component[i] instanceof TextField)
            {
                ((TextField) component[i]).addActionListener(this);
            }
        }

    }

//ActionListener interface
///////////////////////////
    public void actionPerformed(ActionEvent ae)
    {
        Object source = ae.getSource();

        if (source == proxy_button)
        {
            onProxy();
        }
        else if (source == quit_button)
        {
            onQuit();
        }
        else if (source == connect_button || source == port_text
            || source == host_text)
        {
            onConnect();
        }
        else if (source == input_text)
        {
            onInput();
        }
        else if (source == accept_button)
        {
            onAccept();
        }
        else if (source == udp_button)
        {
            onUDP();
        }
        else if (source == clear_button)
        {
            onClear();
        }
    }

//Runnable interface
///////////////////////////////
    public void run()
    {
        boolean finished_OK = true;
        try
        {
            switch (mode)
            {
                case UDP_MODE:
                    startUDP();
                    doUDPPipe();
                    break;
                case LISTEN_MODE:
                    doAccept();
                    doPipe();
                    break;
                case CONNECT_MODE:
                    doConnect();
                    doPipe();
                    break;
                default:
                    warn("Unexpected mode in run() method");
            }

        }
        catch (UnknownHostException uh_ex)
        {
            if (mode != ABORT_MODE)
            {
                finished_OK = false;
                status("Host " + host + " has no DNS entry.");
                uh_ex.printStackTrace();
            }
        }
        catch (IOException io_ex)
        {
            if (mode != ABORT_MODE)
            {
                finished_OK = false;
                status("" + io_ex);
                io_ex.printStackTrace();
            }
        }
        finally
        {
            if (mode == ABORT_MODE)
            {
                status("Connection closed");
            }
            else if (finished_OK)
            {
                status("Connection closed by foreign host.");
            }

            onDisconnect();
        }
    }

//Private methods
////////////////////////////////////////////////////////////////////////
// GUI event handlers.
//////////////////////////
    private void onConnect()
    {
        if (mode == CONNECT_MODE)
        {
            status("Diconnecting...");
            abort_connection();
            return;
        }
        else if (mode != COMMAND_MODE)
        {
            return;
        }

        if (!readHost())
        {
            return;
        }
        if (!readPort())
        {
            return;
        }

        if (proxy == null)
        {
            warn("Proxy is not set");
            onProxy();
            return;
        }

        startNetThread(CONNECT_MODE);
        status("Connecting to " + host + ":" + port + "  ...");

        connect_button.setLabel("Disconnect");
        connect_button.invalidate();
        accept_button.setEnabled(false);
        udp_button.setEnabled(false);
        doLayout();
        input_text.requestFocus();
    }

    private void onDisconnect()
    {
        synchronized (net_lock)
        {
            mode = COMMAND_MODE;
            connect_button.setLabel("Connect");
            accept_button.setLabel("Accept");
            udp_button.setLabel("UDP");
            accept_button.setEnabled(true);
            connect_button.setEnabled(true);
            udp_button.setEnabled(true);
            server_sock = null;
            sock = null;
            out = null;
            in = null;
            net_thread = null;
        }
    }

    private void onAccept()
    {
        if (mode == LISTEN_MODE)
        {
            abort_connection();
            return;
        }
        else if (mode != COMMAND_MODE)
        {
            return;
        }

        if (!readHost())
        {
            return;
        }
        if (!readPort())
        {
            port = 0;
        }

        if (proxy == null)
        {
            warn("Proxy is not set");
            onProxy();
            return;
        }

        startNetThread(LISTEN_MODE);

        accept_button.setLabel("Abort");
        connect_button.setEnabled(false);
        udp_button.setEnabled(false);
        input_text.requestFocus();
    }

    private void onUDP()
    {
        if (mode == UDP_MODE)
        {
            abort_connection();
            return;
        }
        else if (mode == ABORT_MODE)
        {
            return;
        }

        if (proxy == null)
        {
            warn("Proxy is not set");
            onProxy();
            return;
        }

        startNetThread(UDP_MODE);
        udp_button.setLabel("Abort");
        connect_button.setEnabled(false);
        accept_button.setEnabled(false);
        udp_button.invalidate();
        doLayout();
        input_text.requestFocus();
    }

    private void onInput()
    {
        String send_string = input_text.getText() + "\n";
        switch (mode)
        {
            case ABORT_MODE:  //Fall through
            case COMMAND_MODE:
                return;
            case CONNECT_MODE://Fall through
            case LISTEN_MODE:
                synchronized (net_lock)
                {
                    if (out == null)
                    {
                        return;
                    }
                    send(send_string);
                }
                break;
            case UDP_MODE:
                if (!readHost())
                {
                    return;
                }
                if (!readPort())
                {
                    return;
                }
                sendUDP(send_string, host, port);
                break;
            default:
                print("Unknown mode in onInput():" + mode);

        }
        input_text.setText("");
        print(send_string);
    }

    private void onClear()
    {
        output_textarea.setText("");
    }

    private void onProxy()
    {
        Proxy p;
        p = socks_dialog.getProxy(proxy);
        if (p != null)
        {
            proxy = p;
        }
        if (proxy != null && proxy instanceof Socks5Proxy)
        {
            ((Socks5Proxy) proxy).resolveAddrLocally(false);
        }
    }

    private void onQuit()
    {
        dispose();
        System.exit(0);
    }

//Data retrieval functions
//////////////////////////
    /**
     * Reads the port field, returns false if parsing fails.
     */
    private boolean readPort()
    {
        try
        {
            port = Integer.parseInt(port_text.getText());
        }
        catch (NumberFormatException nfe)
        {
            warn("Port invalid!");
            return false;
        }
        return true;
    }

    private boolean readHost()
    {
        host = host_text.getText();
        host.trim();
        if (host.length() < 1)
        {
            warn("Host is not set");
            return false;
        }
        return true;
    }

//Display functions
///////////////////
    private void status(String s)
    {
        status_label.setText(s);
    }

    private void println(String s)
    {
        output_textarea.append(s + "\n");
    }

    private void print(String s)
    {
        output_textarea.append(s);
    }

    private void warn(String s)
    {
        status(s);
        //System.err.println(s);
    }

//Network related functions
////////////////////////////
    private void startNetThread(int m)
    {
        mode = m;
        net_thread = new Thread(this);
        net_thread.start();
    }

    private void abort_connection()
    {
        synchronized (net_lock)
        {
            if (mode == COMMAND_MODE)
            {
                return;
            }
            mode = ABORT_MODE;
            if (net_thread != null)
            {
                try
                {
                    if (sock != null)
                    {
                        sock.close();
                    }
                    if (server_sock != null)
                    {
                        server_sock.close();
                    }
                    if (udp_sock != null)
                    {
                        udp_sock.close();
                    }
                }
                catch (IOException ioe)
                {
                }
                net_thread.interrupt();
                net_thread = null;
            }
        }
    }

    private void doAccept() throws IOException
    {

        println("Trying to accept from " + host);
        status("Trying to accept from " + host);
        println("Using proxy:" + proxy);
        server_sock = new SocksServerSocket(proxy, host, port);

        //server_sock.setSoTimeout(30000);

        println("Listenning on: " + server_sock.getInetAddress()
            + ":" + server_sock.getLocalPort());
        sock = server_sock.accept();
        println("Accepted from:" + sock.getInetAddress() + ":"
            + sock.getPort());

        status("Accepted from:" + sock.getInetAddress().getHostAddress()
            + ":" + sock.getPort());

        server_sock.close(); //Even though this doesn't do anything
    }

    private void doConnect() throws IOException
    {
        println("Trying to connect to:" + host + ":" + port);
        println("Using proxy:" + proxy);
        sock = new SocksSocket(proxy, host, port);
        println("Connected to:" + sock.getInetAddress() + ":" + port);
        status("Connected to: " + sock.getInetAddress().getHostAddress()
            + ":" + port);
        println("Via-Proxy:" + sock.getLocalAddress() + ":"
            + sock.getLocalPort());

    }

    private void doPipe() throws IOException
    {
        out = sock.getOutputStream();
        in = sock.getInputStream();

        byte[] buf = new byte[1024];
        int bytes_read;
        while ((bytes_read = in.read(buf)) > 0)
        {
            print(new String(buf, 0, bytes_read));
        }

    }

    private void startUDP() throws IOException
    {
        udp_sock = new Socks5DatagramSocket(proxy, 0, null);
        println("UDP started on " + udp_sock.getLocalAddress() + ":"
            + udp_sock.getLocalPort());
        status("UDP:" + udp_sock.getLocalAddress().getHostAddress() + ":"
            + udp_sock.getLocalPort());
    }

    private void doUDPPipe() throws IOException
    {
        DatagramPacket dp = new DatagramPacket(new byte[MAX_DATAGRAM_SIZE],
            MAX_DATAGRAM_SIZE);
        while (true)
        {
            udp_sock.receive(dp);
            print("UDP\n"
                + "From:" + dp.getAddress() + ":" + dp.getPort() + "\n"
                + "\n"
                + //Java 1.2
                //new String(dp.getData(),dp.getOffset(),dp.getLength())+"\n"
                //Java 1.1
                new String(dp.getData(), 0, dp.getLength()) + "\n");
            dp.setLength(MAX_DATAGRAM_SIZE);
        }
    }

    private void sendUDP(String message, String host, int port)
    {
        if (!udp_sock.isProxyAlive(100))
        {
            status("Proxy closed connection");
            abort_connection();
            return;
        }

        try
        {
            byte[] data = message.getBytes();
            DatagramPacket dp = new DatagramPacket(data, data.length, null, port);
            udp_sock.send(dp, host);
        }
        catch (UnknownHostException uhe)
        {
            status("Host " + host + " has no DNS entry.");
        }
        catch (IOException ioe)
        {
            status("IOException:" + ioe);
            abort_connection();
        }

    }

    private void send(String s)
    {
        try
        {
            out.write(s.getBytes());
        }
        catch (IOException io_ex)
        {
            println("IOException:" + io_ex);
            abort_connection();
        }
    }


    /*======================================================================
    Form:
    Table:
    +---+---------------+
    |   |               |
    +---+---+---+---+---+
    |   |   |   |   |   |
    +---+---+---+---+---+
    |                   |
    +-------------------+
    |                   |
    +---+---+---+---+---+
    |   |   |   |   |   |
    +---+---+---+---+---+
    |                   |
    +-------------------+
     */
    void guiInit()
    {
        //Some default names used
        Label label;
        Container container;

        GridBagConstraints c = new GridBagConstraints();

        container = this;
        //container = new Panel();
        container.setLayout(new GridBagLayout());
        container.setBackground(SystemColor.menu);
        c.insets = new Insets(3, 3, 3, 3);

        c.gridx = 0;
        c.gridy = 0;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.NORTHEAST;
        label = new Label("Host:");
        container.add(label, c);

        c.gridx = 0;
        c.gridy = 1;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.NORTHEAST;
        label = new Label("Port:");
        container.add(label, c);

        c.gridx = 0;
        c.gridy = 5;
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.gridheight = 1;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.insets = new Insets(0, 0, 0, 0);
        status_label = new Label("");
        container.add(status_label, c);
        c.insets = new Insets(3, 3, 3, 3);


        c.gridx = 1;
        c.gridy = 0;
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.NORTHWEST;
        c.fill = GridBagConstraints.HORIZONTAL;
        host_text = new TextField("");
        container.add(host_text, c);


        c.weightx = 1.0;
        c.fill = GridBagConstraints.NONE;
        c.gridx = 1;
        c.gridy = 1;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.NORTHWEST;
        port_text = new TextField("", 5);
        container.add(port_text, c);

        c.weightx = 0.0;
        c.gridx = 0;
        c.gridy = 3;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.NORTHWEST;
        input_text = new TextField("");
        container.add(input_text, c);

        c.fill = GridBagConstraints.NONE;
        c.gridx = 2;
        c.gridy = 1;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.NORTHEAST;
        connect_button = new Button("Connect");
        container.add(connect_button, c);

        c.gridx = 3;
        c.gridy = 1;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.EAST;
        accept_button = new Button("Accept");
        container.add(accept_button, c);


        c.gridx = 4;
        c.gridy = 1;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.EAST;
        udp_button = new Button("UDP");
        container.add(udp_button, c);


        c.gridx = 0;
        c.gridy = 4;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.NORTHWEST;
        proxy_button = new Button("Proxy...");
        container.add(proxy_button, c);

        c.gridx = 3;
        c.gridy = 4;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.NORTHEAST;
        clear_button = new Button("Clear");
        container.add(clear_button, c);

        c.gridx = 4;
        c.gridy = 4;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.EAST;
        quit_button = new Button("Quit");
        container.add(quit_button, c);

        c.weightx = 1.0;
        c.weighty = 1.0;
        c.fill = GridBagConstraints.BOTH;
        c.gridx = 0;
        c.gridy = 2;
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.NORTHWEST;
        output_textarea = new TextArea("", 10, 50);
        output_textarea.setFont(new Font("Monospaced", Font.PLAIN, 11));
        output_textarea.setEditable(false);
        //output_textarea.setEnabled(false);
        container.add(output_textarea, c);

    }//end guiInit

// WindowListener Interface
/////////////////////////////////
    public void windowActivated(java.awt.event.WindowEvent e)
    {
    }

    public void windowDeactivated(java.awt.event.WindowEvent e)
    {
    }

    public void windowOpened(java.awt.event.WindowEvent e)
    {
    }

    public void windowClosing(java.awt.event.WindowEvent e)
    {
        if (e.getWindow() == this)
        {
            onQuit();
        }
        else
        {
            e.getWindow().dispose();
        }
    }

    public void windowClosed(java.awt.event.WindowEvent e)
    {
    }

    public void windowIconified(java.awt.event.WindowEvent e)
    {
    }

    public void windowDeiconified(java.awt.event.WindowEvent e)
    {
    }

// Main 
////////////////////////////////////
    public static void main(String[] args)
    {
        SocksEcho socksecho = new SocksEcho();
        socksecho.pack();
        socksecho.show();
    }
}//end class

